package com.youlai.quarkus.system.security.jwt;

import cn.hutool.core.convert.Convert;
import cn.hutool.jwt.Claims;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import com.youlai.quarkus.system.common.constant.SecurityConstants;
import com.youlai.quarkus.system.common.redis.IRedisService;
import com.youlai.quarkus.system.security.model.SysUserDetails;
import io.quarkus.security.StringPermission;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.control.ActivateRequestContext;
import jakarta.enterprise.inject.Instance;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import java.nio.charset.StandardCharsets;
import java.security.Permission;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 *  JWT token 管理器
 *
 * @author haoxr
 * @since 2023/9/13
 */

@ApplicationScoped
@RequiredArgsConstructor
public class JwtTokenProvider{

    private final IRedisService redisService;
    private final Instance<SecurityIdentitySupplier> identitySupplierInstance;

    @ConfigProperty(name = "jwt.secret-key", defaultValue = "123456")
    String secretKey;
    @ConfigProperty(name = "jwt.expiration",defaultValue = "7200")
    int expiration;

    private byte[] secretKeyBytes;


    @PostConstruct
    protected void init() {
        secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 创建Token
     *
     * @param authentication
     * @return
     */
    public String createToken(SecurityIdentity authentication) {
        Map<String, Object> claims = new HashMap<>();
        SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
        claims.put("userId", userDetails.getUserId());
        claims.put("username", userDetails.getUsername());
        claims.put("deptId", userDetails.getDeptId());
        claims.put("dataScope", userDetails.getDataScope());
        Collection<String> roles = userDetails.getRoles();
        claims.put("authorities", roles);
        Date now = new Date();
        Date expirationTime = new Date(now.getTime() + expiration * 1000L);
        claims.put(JWTPayload.SUBJECT, userDetails.getUsername());
        claims.put(JWTPayload.EXPIRES_AT, expirationTime);
        claims.put(JWTPayload.ISSUED_AT, now);
        // 权限数据多放入Redis
        Set<String> perms = userDetails.getPerms();
        redisService.set(SecurityConstants.USER_PERMS_CACHE_PREFIX + userDetails.getUserId(), perms);
        // 在生成JWt令牌时这里我没有指定签名算法，默认使用的HS256签名算法
        return JWTUtil.createToken(claims, secretKeyBytes);
    }

    /**
     * https://quarkus.io/guides/security-customization#security-identity-customization
     * redis是阻塞的，如果在认证过程中使用到阻塞io，需要使用以下写法。直接使用会抛出异常
     * mybatis 也是同样
     *
     * @param token
     * @param context
     * @return
     */
    public Uni<SecurityIdentity> getAuthentication(String token, AuthenticationRequestContext context) {
        // Hibernate ORM is blocking
        SecurityIdentitySupplier identitySupplier = identitySupplierInstance.get();
        identitySupplier.setSupplier(() -> this.getTokenClaims(token));
        identitySupplier.setRedisService(redisService);
        return context.runBlocking(identitySupplier);
    }



    public String resolveToken(String token) {
        return null;
    }

    public boolean validateToken(String token) {
        return JWTUtil.verify(token, secretKeyBytes);
    }

    public String getUsername(String token) {
        Object payload = JWTUtil.parseToken(token).setKey(secretKeyBytes).getPayload(JWTPayload.SUBJECT);
        return (String) payload;
    }


    public Claims getTokenClaims(String token) {
        JWT jwt = JWTUtil.parseToken(token);
        jwt.setKey(secretKeyBytes);
        return jwt.getPayload();
    }

    @Dependent
    @Setter
    static class SecurityIdentitySupplier implements Supplier<SecurityIdentity> {

        private IRedisService redisService;
        private Supplier<Claims> supplier;

        @Override
        @ActivateRequestContext
        public SecurityIdentity get() {
            Claims claims = supplier.get();
            SysUserDetails userDetails = new SysUserDetails();
            userDetails.setUserId(Convert.toLong(claims.getClaim("userId"))); // 用户ID
            userDetails.setUsername(Convert.toStr(claims.getClaim("username"))); // 用户名
            userDetails.setDeptId(Convert.toLong(claims.getClaim("deptId"))); // 部门ID
            userDetails.setDataScope(Convert.toInt(claims.getClaim("dataScope"))); // 数据权限范围
            userDetails.setRoles(Convert.toSet(String.class, claims.getClaim("authorities")));
            QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
            String[] perms = redisService.get(SecurityConstants.USER_PERMS_CACHE_PREFIX + userDetails.getUserId(), String[].class);
            builder.setPrincipal(userDetails)
                    .addRoles(new HashSet<>(userDetails.getRoles()))
                    .addPermissionCheckers(addPermissionlist(perms))
                    .build();
            return builder.build();
        }

        private List<Function<Permission, Uni<Boolean>>> addPermissionlist(String[] perms) {
            Map<String, List<String>> permissionMap = new HashMap<>();
            for (String perm : perms) {
                int first = perm.indexOf(":");
                String name = perm;
                String p = null;
                if (first > 0) {
                    name = perm.substring(0, first);
                    p = perm.substring(first);
                }
                List<String> permList = permissionMap.computeIfAbsent(name, k -> new ArrayList<>());
                if (p != null) {
                    permList.add(p);
                }
            }
            return permissionMap.entrySet().stream().map(en -> {
                return new Function<Permission, Uni<Boolean>>() {
                    @Override
                    public Uni<Boolean> apply(Permission permission) {
                        return Uni.createFrom().item(permission.implies(new StringPermission(en.getKey(), en.getValue().toArray(new String[0]))));
                    }
                };
            }).collect(Collectors.toList());

        }

    }


}
